package com.yifeng.repo.base.utils.converter;

import com.yifeng.repo.base.utils.common.BaseUtil;
import org.w3c.dom.*;
import org.xml.sax.InputSource;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by daibing on 2021/11/19.
 */
public class SimpleXmlHelper {
    private static final DocumentBuilderFactory DBF = DocumentBuilderFactory.newInstance();
    private static final TransformerFactory TFF = TransformerFactory.newInstance();
    private static final Transformer TF;

    static {
        try {
            TF = TFF.newTransformer();
            TF.setOutputProperty("encoding", StandardCharsets.UTF_8.name());
            TF.setOutputProperty(OutputKeys.INDENT, "yes");
            DBF.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            DBF.setFeature("http://xml.org/sax/features/external-general-entities", false);
            DBF.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            DBF.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            DBF.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            DBF.setXIncludeAware(false);
            DBF.setExpandEntityReferences(false);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String extract(String xml, String... keys) {
        Node node = findNode(xml, keys);
        if (node == null) {
            return null;
        }
        return node.getTextContent();
    }

    public static Map<String, Object> fromXml(String xml, String... keys) {
        Node node = findNode(xml, keys);
        if (node == null) {
            return null;
        }
        return buildObject(node);
    }

    public static Map<String, Object> fromXmlList(String xml, String... keys) {
        Node node = findNode(xml, keys);
        if (node == null) {
            return null;
        }
        return buildArray(node);
    }

    private static Node findNode(String xml, String... keys) {
        StringReader sr = new StringReader(xml);
        InputSource is = new InputSource(sr);
        try {
            DocumentBuilder db = DBF.newDocumentBuilder();
            Document document = db.parse(is);
            Element root = document.getDocumentElement();
            Node node = root;
            for (int i = 0; i < keys.length; i++) {
                if (i == 0 && root.getNodeName().equals(keys[i])) {
                    continue;
                }
                node = match(node.getChildNodes(), keys[i]);
                if (node == null) {
                    break;
                }
            }
            return node;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Node match(NodeList childNodes, String key) {
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node node = childNodes.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(key)) {
                return node;
            }
        }
        return null;
    }

    private static Map<String, Object> buildObject(Node node) {
        Map<String, Object> data = new HashMap<>();
        if (node.getChildNodes().getLength() > 0) {
            putAll(data, copyChildNode(node.getChildNodes()));
        }
        if (node.getAttributes().getLength() > 0) {
            putAll(data, copyAttr(node.getAttributes()));
        }
        if (data.size() == 0) {
            return null;
        }
        Map<String, Object> result = new HashMap<>(1);
        result.put(node.getNodeName(), data);
        return result;
    }

    private static Map<String, Object> buildArray(Node node) {
        Set<Object> arr = new HashSet<>();
        NodeList childNodes = node.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node row = childNodes.item(i);
            if (row.getNodeType() == Node.ELEMENT_NODE) {
                Map<String, Object> subData = buildObject(row);
                if (subData != null) {
                    arr.add(subData.get(row.getNodeName()));
                }
            }
        }
        Map<String, Object> data = new HashMap<>();
        if (arr.size() > 0) {
            data.put(node.getNodeName(), arr);
        }
        if (node.getAttributes().getLength() > 0) {
            putAll(data, copyAttr(node.getAttributes()));
        }
        Map<String, Object> result = new HashMap<>(1);
        result.put(node.getNodeName(), data);
        return result;
    }

    private static Map<String, Object> copyChildNode(NodeList childNodes) {
        Map<String, Object> child = new HashMap<>();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node item = childNodes.item(i);
            if (item.getNodeType() == Node.ELEMENT_NODE) {
                if (item.getChildNodes().getLength() <= 1 && item.getAttributes().getLength() == 0) {
                    child.put(item.getNodeName(), item.getTextContent());
                    continue;
                }
                Map<String, Object> subData = buildObject(item);
                if (subData == null) {
                    continue;
                }
                if (child.containsKey(item.getNodeName())) {
                    child.put(item.getNodeName() + i, subData.get(item.getNodeName()));
                } else {
                    putAll(child, subData);
                }
            } else if (item.getNodeType() == Node.TEXT_NODE && !BaseUtil.isBlank(item.getTextContent().trim())) {
                child.put("text", item.getTextContent());
            }
        }
        return child;
    }

    private static Map<String, Object> copyAttr(NamedNodeMap attributes) {
        Map<String, Object> attr = new HashMap<>();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node item = attributes.item(i);
            if (item.getNodeType() == Node.ATTRIBUTE_NODE) {
                attr.put(item.getNodeName(), item.getTextContent());
            }
        }
        return attr;
    }

    private static void putAll(Map<String, Object> bucket, Map<String, Object> addData) {
        for (Map.Entry<String, Object> entry : addData.entrySet()) {
            bucket.put(entry.getKey(), entry.getValue());
        }
    }

    public static <K, V> String toXml(Map<K, V> map) {
        return toXml(map, "xml");
    }

    public static <K, V> String toXml(Map<K, V> map, String rootName) {
        try {
            DocumentBuilder db = DBF.newDocumentBuilder();
            Document document = db.newDocument();
            document.setXmlStandalone(true);
            Element root = document.createElement(rootName);
            document.appendChild(root);
            for (Map.Entry<K, V> property : map.entrySet()) {
                Element element = document.createElement(property.getKey().toString());
                element.appendChild(document.createTextNode(String.valueOf(property.getValue())));
                root.appendChild(element);
            }

            DOMSource domSource = new DOMSource(document);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            TF.transform(domSource, new StreamResult(bos));
            return bos.toString(StandardCharsets.UTF_8.name());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String clear(String xml) {
        StringBuilder sb = new StringBuilder(xml.length());
        for (int i = 0; i < xml.length(); ++i) {
            char ch = xml.charAt(i);
            if (Character.isDefined(ch)
                    && !Character.isHighSurrogate(ch) && !Character.isISOControl(ch) && !Character.isLowSurrogate(ch)
                    && ch != '&' /*&& ch != '<' && ch != '>'*/) {
                sb.append(ch);
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println("parse xml start to do...");


        String xml10 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "\n" +
                "<Response service=\"RouteService\">\n" +
                "  <Head>OK</Head>\n" +
                "  <Body>\n" +
                "    <RouteResponse mailno=\"444014944229\" orderid=\"linkasd2221271\">\n" +
                "      <Route remark=\"顺丰速运 已收取快件（测试数据）\" accept_time=\"2018-05-01 08:01:44\" accept_address=\"广东省深圳市软件产业基地\" opcode=\"50\"/>\n" +
                "      <Route remark=\"已签收,感谢使用顺丰,期待再次为您服务（测试数据）\" accept_time=\"2018-05-02 12:01:44\" accept_address=\"广东省深圳市软件产业基地\" opcode=\"80\"/>\n" +
                "    </RouteResponse>\n" +
                "  </Body>\n" +
                "</Response>";
        System.out.println("clear: " + clear(xml10));
        System.out.println("total: " + SimpleXmlHelper.fromXml(clear(xml10)));
        System.out.println("total: " + SimpleXmlHelper.fromXml(clear(xml10), "Response"));
        System.out.println("total2: " + SimpleXmlHelper.fromXmlList(clear(xml10), "Response", "Body", "RouteResponse"));


        String xml9 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "\n" +
                "<Response>\n" +
                "  <logisticProviderID>YTO</logisticProviderID>\n" +
                "  <txLogisticID>linkasd2221270</txLogisticID>\n" +
                "  <clientID>K731162798</clientID>\n" +
                "  <mailNo>820452219951</mailNo>\n" +
                "  <distributeInfo>\n" +
                "    <shortAddress>300-147-00-203</shortAddress>\n" +
                "    <consigneeBranchCode>210164</consigneeBranchCode>\n" +
                "    <packageCenterCode>210901</packageCenterCode>\n" +
                "    <packageCenterName/>\n" +
                "  </distributeInfo>\n" +
                "  <code>200</code>\n" +
                "  <success>true</success>\n" +
                "</Response>";
        System.out.println("total: " + SimpleXmlHelper.fromXml(xml9, "Response"));


        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<ListAllMyBucketsResult>\n" +
                "  <Owner>\n" +
                "    <ID>1902495060906825</ID>\n" +
                "    <DisplayName>1902495060906825</DisplayName>\n" +
                "  </Owner>\n" +
                "  <Buckets>\n" +
                "    <Bucket>\n" +
                "      <Comment></Comment>\n" +
                "      <CreationDate>2019-03-04T06:55:25.000Z</CreationDate>\n" +
                "      <ExtranetEndpoint>oss-cn-shenzhen.aliyuncs.com</ExtranetEndpoint>\n" +
                "      <IntranetEndpoint>oss-cn-shenzhen-internal.aliyuncs.com</IntranetEndpoint>\n" +
                "      <Location>oss-cn-shenzhen</Location>\n" +
                "      <Name>x-link-bucket-dev-brm</Name>\n" +
                "      <StorageClass>Standard</StorageClass>\n" +
                "    </Bucket>\n" +
                "    <Bucket>\n" +
                "      <Comment></Comment>\n" +
                "      <CreationDate>2019-03-04T06:59:34.000Z</CreationDate>\n" +
                "      <ExtranetEndpoint>oss-cn-shenzhen.aliyuncs.com</ExtranetEndpoint>\n" +
                "      <IntranetEndpoint>oss-cn-shenzhen-internal.aliyuncs.com</IntranetEndpoint>\n" +
                "      <Location>oss-cn-shenzhen</Location>\n" +
                "      <Name>x-link-bucket-dev-brm-2</Name>\n" +
                "      <StorageClass>Standard</StorageClass>\n" +
                "    </Bucket>\n" +
                "  </Buckets>\n" +
                "</ListAllMyBucketsResult>";
        System.out.println("value: " + extract(xml, "ListAllMyBucketsResult", "Buckets", "Bucket", "IntranetEndpoint"));
        System.out.println("result : " + fromXml(xml, "ListAllMyBucketsResult", "Buckets", "Bucket", "IntranetEndpoint"));
        System.out.println("result : " + fromXmlList(xml, "ListAllMyBucketsResult", "Buckets"));

        String xml2 = "<Subscriptions xmlns=\"http://mns.aliyuncs.com/doc/v1\">\n" +
                "  <Subscription>\n" +
                "    <SubscriptionURL>http://1152963324702913.mns.cn-shanghai.aliyuncs.com/topics/x-link-topic-dev/subscriptions/test116</SubscriptionURL>\n" +
                "  </Subscription>\n" +
                "  <Subscription>\n" +
                "    <SubscriptionURL>http://1152963324702913.mns.cn-shanghai.aliyuncs.com/topics/x-link-topic-dev/subscriptions/test117</SubscriptionURL>\n" +
                "  </Subscription>\n" +
                "</Subscriptions>";
        System.out.println("result21: " + fromXmlList(xml2, "Subscriptions"));

        String xml3 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<Response service=\"RouteService\">\n" +
                "  <Head>OK</Head>\n" +
                "  <Body>\n" +
                "    <RouteResponse mailno=\"444013958894\" orderid=\"linkasd222d42\">\n" +
                "      <Route remark=\"顺丰速运 已收取快件（测试数据）\" accept_time=\"2018-05-01 08:01:44\" accept_address=\"广东省深圳市软件产业基地\" opcode=\"50\"/>\n" +
                "      <Route remark=\"已签收,感谢使用顺丰,期待再次为您服务（测试数据）\" accept_time=\"2018-05-02 12:01:44\" accept_address=\"广东省深圳市软件产业基地\" opcode=\"80\"/>\n" +
                "    </RouteResponse>\n" +
                "  </Body>\n" +
                "</Response>";
        System.out.println("Head31: " + extract(xml3, "Response", "Head"));
        System.out.println("Head32: " + fromXml(xml3, "Response", "Body", "RouteResponse", "Route"));
        System.out.println("Head33: " + fromXmlList(xml3, "Response", "Body", "RouteResponse","Route"));


        String xml4 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<Response service=\"OrderService\">\n" +
                "  <Head>OK</Head>\n" +
                "  <Body>\n" +
                "    \t<OrderResponse filter_result=\"2\" mailno=\"444014642284\" orderid=\"linkasd222d45\">\n" +
                "     \t\t <rls_info rls_errormsg=\"444014642284:必填字段为空!\" invoke_result=\"OK\" rls_code=\"0004\"/>\n" +
                "    \t</OrderResponse>\n" +
                "  </Body>\n" +
                "</Response>";
        System.out.println("Head41: " + extract(xml4, "Response", "Head"));
        System.out.println("Head42: " + fromXml(xml4, "Response", "Body", "OrderResponse", "rls_info"));
        System.out.println("Head43: " + fromXmlList(xml4, "Response", "Body", "OrderResponse"));

        String xml5 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<Response service=\"OrderService\">\n" +
                "  \t<Head>ERR</Head>\n" +
                "  \t<ERROR code=\"8016\">重复下单</ERROR>\n" +
                "</Response>";
        System.out.println("Head51: " + extract(xml5, "Response", "Head"));
        System.out.println("Head52: " + fromXml(xml5, "Response", "ERROR"));


        String xml6 = "<Result> \n" +
                "    <WaybillProcessInfo> \n" +
                "      <Waybill_No>500121003301</Waybill_No>  \n" +
                "      <Upload_Time>2015/5/5 19:34:55</Upload_Time>  \n" +
                "      <ProcessInfo>北京市海淀区中关村公司 已揽收</ProcessInfo> \n" +
                "    </WaybillProcessInfo>  \n" +
                "    <WaybillProcessInfo> \n" +
                "      <Waybill_No>500121003301</Waybill_No>  \n" +
                "      <Upload_Time>2015/5/5 20:13:20</Upload_Time>  \n" +
                "      <ProcessInfo>北京市海淀区中关村公司 已打包,发往下一站 济南转运中心</ProcessInfo> \n" +
                "    </WaybillProcessInfo>  \n" +
                "    <WaybillProcessInfo> \n" +
                "      <Waybill_No>500121003301</Waybill_No>  \n" +
                "      <Upload_Time>2015/5/6 10:21:10</Upload_Time>  \n" +
                "      <ProcessInfo>济南转运中心公司 已发出,下一站 山东省济南市千佛山</ProcessInfo> \n" +
                "    </WaybillProcessInfo>  \n" +
                "    <WaybillProcessInfo> \n" +
                "      <Waybill_No>500121003301</Waybill_No>  \n" +
                "      <Upload_Time>2015/5/6 13:36:31</Upload_Time>  \n" +
                "      <ProcessInfo>快件到达 山东省济南市千佛山公司</ProcessInfo> \n" +
                "    </WaybillProcessInfo>  \n" +
                "    <WaybillProcessInfo> \n" +
                "      <Waybill_No>500121003301</Waybill_No>  \n" +
                "      <Upload_Time>2015/5/6 13:41:55</Upload_Time>  \n" +
                "      <ProcessInfo>山东省济南市千佛山公司 派件人: ***** 派件中 派件员电话1234567891</ProcessInfo> \n" +
                "    </WaybillProcessInfo> \n" +
                "  </Result> ";

        System.out.println("Head42: " + fromXml(xml6, "Result"));
        System.out.println("Head43: " + fromXmlList(xml6, "Result"));

        String xml7 ="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "\n" +
                "<Request service=\"RoutePushService\" lang=\"zh-CN\">\n" +
                "  <Body>\n" +
                "    <WaybillRoute id=\"684520153\" mailno=\"974982619267\" orderid=\"XINGREN_636959c0f41f6d5cf86cf0b42d8a\" acceptTime=\"2017-09-21 11:14:43\" acceptAddress=\"烟台市\" remark=\"在官网&quot;运单资料&amp;签收图&quot;,可查看签收人信息在官网&quot;运单资料&amp;签收图&quot;,可查看签收人信息\" opCode=\"8000\"/>\n" +
                "  </Body>\n" +
                "</Request>\n";

        System.out.println("Head32: " + fromXml(xml7, "Request", "Body", "WaybillRoute"));
        System.out.println("Head33: " + fromXmlList(xml7, "Request", "Body", "WaybillRoute"));

    }

}
